| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171 |
- 'use client';
- import { use, useEffect, useState, type CSSProperties } from 'react';
- import { useDonationHub } from '@/hooks/useDonationHub';
- import { fetchApi } from '@/lib/utils/client';
- import { CrewRankItem } from '@/types/donation';
- import './style.scss';
- type Props = {
- params: Promise<{ widgetToken: string }>;
- searchParams: Promise<{ [key: string]: string|string[]|undefined }>;
- };
- type CrewStyleConfig = {
- id: number;
- crewID: number;
- crewName: string;
- title: string;
- theme: number;
- period: number;
- maxDisplayCount: number;
- isShowAmount: boolean;
- isShowDonationCount: boolean;
- isShowContributionRate: boolean;
- isShowMemberIcon: boolean;
- isActive: boolean;
- bgColor: string;
- titleFontFamily: string|null;
- titleFontSizePx: number;
- titleFontColor: string;
- rank1FontFamily: string|null;
- rank1FontSizePx: number;
- rank1FontColor: string;
- rank2FontFamily: string|null;
- rank2FontSizePx: number;
- rank2FontColor: string;
- rank3FontFamily: string|null;
- rank3FontSizePx: number;
- rank3FontColor: string;
- rowFontFamily: string|null;
- rowFontSizePx: number;
- rowFontColor: string;
- };
- type CrewRankResponse = {
- list: CrewRankItem[];
- totalAmount: number;
- };
- const THEME_NAMES = ['basic', 'dark', 'minimal'];
- export default function CrewLeaderboardPage({ params, searchParams }: Props) {
- const { widgetToken } = use(params);
- const sp = use(searchParams);
- const configID = typeof sp.configID === 'string' ? parseInt(sp.configID, 10) : null;
- const hubUrl = process.env.NEXT_PUBLIC_API_URL + '/hubs/donation';
- const { crewRanking, setCrewRanking } = useDonationHub(widgetToken, hubUrl);
- const [styleCfg, setStyleCfg] = useState<CrewStyleConfig|null>(null);
- // 스타일 config 로드
- useEffect(() => {
- const qs = configID ? `?configID=${configID}` : '';
- fetchApi<CrewStyleConfig>(`/api/widget/crew/config/${widgetToken}${qs}`, { silent: true }).then(res => {
- if (res.data) {
- setStyleCfg(res.data);
- }
- }).catch(() => {});
- }, [widgetToken, configID]);
- // 순위 데이터 로드 (CrewMember 기반 base — 후원 없어도 0원으로 표시)
- useEffect(() => {
- const qs = configID ? `?configID=${configID}` : '';
- fetchApi<CrewRankResponse>(`/api/widget/crew/by-token/${widgetToken}${qs}`, { silent: true }).then(res => {
- if (res.data?.list) {
- setCrewRanking(res.data.list);
- }
- }).catch(() => {});
- }, [widgetToken, configID, setCrewRanking]);
- const themeName = styleCfg ? (THEME_NAMES[styleCfg.theme] ?? 'basic') : 'basic';
- const title = styleCfg?.title ?? '크루 순위';
- const isShowAmount = styleCfg?.isShowAmount ?? true;
- const isShowContributionRate = styleCfg?.isShowContributionRate ?? true;
- const isShowDonationCount = styleCfg?.isShowDonationCount ?? false;
- const isShowMemberIcon = styleCfg?.isShowMemberIcon ?? true;
- const bodyStyle = { '--crew-bg': styleCfg?.bgColor ?? '#1A1A2E' } as CSSProperties;
- const titleStyle = {
- '--crew-title-font': styleCfg?.titleFontFamily ?? 'inherit',
- '--crew-title-size': `${styleCfg?.titleFontSizePx ?? 18}px`,
- '--crew-title-color': styleCfg?.titleFontColor ?? '#FFFFFF'
- } as CSSProperties;
- const getFontStyle = (rank: number): CSSProperties => {
- if (rank === 1 && styleCfg) {
- return {
- '--row-font-family': styleCfg.rank1FontFamily ?? 'inherit',
- '--row-font-size': `${styleCfg.rank1FontSizePx}px`,
- '--row-font-color': styleCfg.rank1FontColor
- } as CSSProperties;
- }
- if (rank === 2 && styleCfg) {
- return {
- '--row-font-family': styleCfg.rank2FontFamily ?? 'inherit',
- '--row-font-size': `${styleCfg.rank2FontSizePx}px`,
- '--row-font-color': styleCfg.rank2FontColor
- } as CSSProperties;
- }
- if (rank === 3 && styleCfg) {
- return {
- '--row-font-family': styleCfg.rank3FontFamily ?? 'inherit',
- '--row-font-size': `${styleCfg.rank3FontSizePx}px`,
- '--row-font-color': styleCfg.rank3FontColor
- } as CSSProperties;
- }
- return {
- '--row-font-family': styleCfg?.rowFontFamily ?? 'inherit',
- '--row-font-size': `${styleCfg?.rowFontSizePx ?? 14}px`,
- '--row-font-color': styleCfg?.rowFontColor ?? '#FFFFFF'
- } as CSSProperties;
- };
- const getBadgeClass = (rank: number) => rank <= 3 ? `crew-widget__badge--${rank}` : 'crew-widget__badge--default';
- return (
- <div className={`crew-widget crew-widget--${themeName}`} style={bodyStyle}>
- <div className="crew-widget__title" style={titleStyle}>
- {title}
- </div>
- <div className="crew-widget__columns">
- <span className="crew-widget__col-rank">순위</span>
- {isShowMemberIcon && <span className="crew-widget__col-icon" />}
- <span className="crew-widget__col-name">이름</span>
- {isShowContributionRate && <span className="crew-widget__col-rate">기여도</span>}
- {isShowAmount && <span className="crew-widget__col-amount">후원 점수</span>}
- {isShowDonationCount && <span className="crew-widget__col-count">건수</span>}
- </div>
- <div className="crew-widget__list">
- {crewRanking.map(item => {
- const style = getFontStyle(item.rank);
- return (
- <div
- key={item.crewMemberID}
- className={`crew-widget__item${item.rank <= 3 ? ` crew-widget__item--${item.rank}` : ''}`}
- style={style}
- >
- <span className={`crew-widget__badge ${getBadgeClass(item.rank)}`}>{item.rank}</span>
- {isShowMemberIcon && (
- item.icon
- ? <img className="crew-widget__member-icon" src={item.icon} alt={item.nickname} />
- : <span className="crew-widget__member-icon crew-widget__member-icon--empty" />
- )}
- <span className="crew-widget__name">{item.nickname}</span>
- {isShowContributionRate && (
- <span className="crew-widget__rate">{item.contributionRate.toFixed(1)}%</span>
- )}
- {isShowAmount && (
- <span className="crew-widget__amount">{item.totalAmount.toLocaleString()}원</span>
- )}
- {isShowDonationCount && (
- <span className="crew-widget__count">{item.donationCount}건</span>
- )}
- </div>
- );
- })}
- </div>
- </div>
- );
- }
|